diff --git a/cypress/integration/admin.spec.js b/cypress/integration/admin.spec.js index 12d3fd67..6e856ee1 100644 --- a/cypress/integration/admin.spec.js +++ b/cypress/integration/admin.spec.js @@ -1,208 +1,205 @@ /** - * Copyright (C) 2019 The Software Heritage developers + * Copyright (C) 2019-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ const $ = Cypress.$; const defaultRedirect = '/admin/origin/save/'; let url; function logout() { cy.contains('a', 'logout') .click(); } describe('Test Admin Login/logout', function() { before(function() { url = this.Urls.admin(); }); it('should redirect to default page', function() { cy.visit(url) .get('input[name="username"]') .type('admin') .get('input[name="password"]') .type('admin') .get('.container form') .submit(); cy.location('pathname') .should('be.equal', defaultRedirect); logout(); }); it('should display admin-origin-save and deposit in sidebar', function() { cy.adminLogin(); cy.visit(url); cy.get(`.sidebar a[href="${this.Urls.admin_origin_save()}"]`) .should('be.visible'); cy.get(`.sidebar a[href="${this.Urls.admin_deposit()}"]`) .should('be.visible'); logout(); }); it('should display username on top-right', function() { cy.adminLogin(); cy.visit(url); cy.get('.swh-position-right') .should('contain', 'admin'); logout(); }); it('should prevent unauthorized access after logout', function() { cy.visit(this.Urls.admin_origin_save()) .location('pathname') .should('be.equal', '/admin/login/'); cy.visit(this.Urls.admin_deposit()) .location('pathname') .should('be.equal', '/admin/login/'); }); it('should redirect to correct page after login', function() { // mock calls to deposit list api to avoid possible errors // while running the test - cy.server(); - cy.route({ - method: 'GET', - url: `${this.Urls.admin_deposit_list()}**`, - response: { + cy.intercept(`${this.Urls.admin_deposit_list()}**`, { + body: { data: [], recordsTotal: 0, recordsFiltered: 0, draw: 1 } }); cy.visit(this.Urls.admin_deposit()) .location('search') .should('contain', `next=${this.Urls.admin_deposit()}`); cy.adminLogin(); cy.visit(this.Urls.admin_deposit()); cy.location('pathname') .should('be.equal', this.Urls.admin_deposit()); logout(); }); }); const existingRowToSelect = 'https://bitbucket.org/'; const originUrlListTestData = [ { listType: 'authorized', originToAdd: 'git://git.archlinux.org/', originToRemove: 'https://github.com/' }, { listType: 'unauthorized', originToAdd: 'https://random.org', originToRemove: 'https://gitlab.com' } ]; const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1); describe('Test Admin Origin Save Urls Filtering', function() { beforeEach(function() { cy.adminLogin(); cy.visit(this.Urls.admin_origin_save()); cy.contains('a', 'Origin urls filtering') .click() .wait(500); }); it(`should select or unselect a table row by clicking on it`, function() { cy.contains(`#swh-authorized-origin-urls tr`, existingRowToSelect) .click() .should('have.class', 'selected') .click() .should('not.have.class', 'selected'); }); originUrlListTestData.forEach(testData => { it(`should add a new origin url prefix in the ${testData.listType} list`, function() { const tabName = capitalize(testData.listType) + ' urls'; cy.contains('a', tabName) .click() .wait(500); cy.get(`#swh-${testData.listType}-origin-urls tr`).each(elt => { if ($(elt).text() === testData.originToAdd) { cy.get(elt).click(); cy.get(`#swh-remove-${testData.listType}-origin-url`).click(); } }); cy.get(`#swh-${testData.listType}-url-prefix`) .type(testData.originToAdd); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToAdd) .should('be.visible'); cy.contains('.alert-success', `The origin url prefix has been successfully added in the ${testData.listType} list.`) .should('be.visible'); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.contains('.alert-warning', `The provided origin url prefix is already registered in the ${testData.listType} list.`) .should('be.visible'); }); it(`should remove an origin url prefix from the ${testData.listType} list`, function() { const tabName = capitalize(testData.listType) + ' urls'; cy.contains('a', tabName) .click(); let originUrlMissing = true; cy.get(`#swh-${testData.listType}-origin-urls tr`).each(elt => { if ($(elt).text() === testData.originToRemove) { originUrlMissing = false; } }); if (originUrlMissing) { cy.get(`#swh-${testData.listType}-url-prefix`) .type(testData.originToRemove); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.get('.alert-dismissible button').click(); } cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToRemove) .click(); cy.get(`#swh-remove-${testData.listType}-origin-url`).click(); cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToRemove) .should('not.exist'); }); }); }); diff --git a/cypress/integration/api-tokens.spec.js b/cypress/integration/api-tokens.spec.js index 24d470c6..7e1c3a22 100644 --- a/cypress/integration/api-tokens.spec.js +++ b/cypress/integration/api-tokens.spec.js @@ -1,140 +1,133 @@ /** - * Copyright (C) 2020 The Software Heritage developers + * Copyright (C) 2020-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ describe('Test API tokens UI', function() { it('should ask for user to login', function() { cy.visit(`${this.Urls.oidc_profile()}#tokens`, {failOnStatusCode: false}); cy.location().should(loc => { expect(loc.pathname).to.eq(this.Urls.oidc_login()); }); }); function initTokensPage(Urls, tokens) { - cy.server(); - cy.route({ - method: 'GET', - url: `${Urls.oidc_list_bearer_tokens()}/**`, - response: { + cy.intercept(`${Urls.oidc_list_bearer_tokens()}/**`, { + body: { 'recordsTotal': tokens.length, 'draw': 2, 'recordsFiltered': tokens.length, 'data': tokens } }); // the tested UI should not be accessible for standard Django users // but we need a user logged in for testing it cy.adminLogin(); cy.visit(`${Urls.oidc_profile()}#tokens`); } it('should initiate token generation flow', function() { initTokensPage(this.Urls, []); cy.contains('Generate new token') .click(); cy.get('.modal-dialog') .should('be.visible'); cy.get('.modal-header') .should('contain', 'Bearer token generation'); cy.get('#swh-token-form-submit') .click(); cy.location().should(loc => { expect(loc.pathname).to.eq(this.Urls.oidc_generate_bearer_token()); }); }); it('should report error when not logged in and visiting a token generation URL', function() { cy.visit(this.Urls.oidc_generate_bearer_token_complete(), {failOnStatusCode: false}); cy.get('.swh-http-error') .should('be.visible'); cy.get('.swh-http-error-code') .should('contain', 403); cy.get('.swh-http-error-desc') .should('contain', 'You are not allowed to generate bearer tokens'); }); function displayToken(Urls, status, tokenValue = '') { - cy.route({ - method: 'POST', - url: `${Urls.oidc_get_bearer_token()}/**`, - response: tokenValue, - status: status + cy.intercept('POST', `${Urls.oidc_get_bearer_token()}/**`, { + body: tokenValue, + statusCode: status }).as('getTokenRequest'); cy.contains('Display token') .click({force: true}); cy.get('.modal-dialog') .should('be.visible'); cy.get('.modal-header') .should('contain', 'Display bearer token'); } it('should show a token when requested', function() { initTokensPage(this.Urls, [{id: 1, creation_date: new Date().toISOString()}]); const tokenValue = 'token-value'; displayToken(this.Urls, 200, tokenValue); cy.get('#swh-bearer-token') .should('contain', tokenValue); }); it('should report errors when token display failed', function() { initTokensPage(this.Urls, [{id: 1, creation_date: new Date().toISOString()}]); displayToken(this.Urls, 500); cy.get('.modal-body') .should('contain', 'Internal server error'); }); function revokeToken(Urls, status) { - cy.route({ - method: 'POST', - url: `${Urls.oidc_revoke_bearer_tokens()}/**`, - response: '', - status: status + cy.intercept('POST', `${Urls.oidc_revoke_bearer_tokens()}/**`, { + body: '', + statusCode: status }).as('revokeTokenRequest'); cy.contains('Revoke token') .click({force: true}); cy.get('.modal-dialog') .should('be.visible'); cy.get('.modal-header') .should('contain', 'Revoke bearer token'); cy.get('#swh-token-form-submit') .click({force: true}); cy.wait('@revokeTokenRequest'); if (status === 200) { cy.get('#swh-token-form-submit') .should('be.disabled'); } } it('should revoke a token when requested', function() { initTokensPage(this.Urls, [{id: 1, creation_date: new Date().toISOString()}]); revokeToken(this.Urls, 200); cy.get('#swh-token-form-message') .should('contain', 'Bearer token successfully revoked'); }); it('should report errors when token revoke failed', function() { initTokensPage(this.Urls, [{id: 1, creation_date: new Date().toISOString()}]); revokeToken(this.Urls, 500); cy.get('#swh-token-error-message') .should('contain', 'Internal server error'); }); }); diff --git a/cypress/integration/deposit-admin.spec.js b/cypress/integration/deposit-admin.spec.js index c5ddeb03..b0e78f11 100644 --- a/cypress/integration/deposit-admin.spec.js +++ b/cypress/integration/deposit-admin.spec.js @@ -1,159 +1,156 @@ /** - * Copyright (C) 2020 The Software Heritage developers + * Copyright (C) 2020-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ // data to use as request query response let responseDeposits; let expectedOrigins; describe('Test admin deposit page', function() { beforeEach(() => { responseDeposits = [ { 'id': 614, 'external_id': 'ch-de-1', 'reception_date': '2020-05-18T13:48:27Z', 'status': 'done', 'status_detail': null, 'swhid': 'swh:1:dir:ef04a768', 'swhid_context': 'swh:1:dir:ef04a768;origin=https://w.s.o/c-d-1;visit=swh:1:snp:b234be1e;anchor=swh:1:rev:d24a75c9;path=/' }, { 'id': 613, 'external_id': 'ch-de-2', 'reception_date': '2020-05-18T11:20:16Z', 'status': 'done', 'status_detail': null, 'swhid': 'swh:1:dir:181417fb', 'swhid_context': 'swh:1:dir:181417fb;origin=https://w.s.o/c-d-2;visit=swh:1:snp:8c32a2ef;anchor=swh:1:rev:3d1eba04;path=/' }, { 'id': 612, 'external_id': 'ch-de-3', 'reception_date': '2020-05-18T11:20:16Z', 'status': 'rejected', 'status_detail': 'incomplete deposit!', 'swhid': null, 'swhid_context': null } ]; // those are computed from the expectedOrigins = { 614: 'https://w.s.o/c-d-1', 613: 'https://w.s.o/c-d-2', 612: '' }; }); it('Should display properly entries', function() { cy.adminLogin(); cy.visit(this.Urls.admin_deposit()); let testDeposits = responseDeposits; - cy.server(); - cy.route({ - method: 'GET', - url: `${this.Urls.admin_deposit_list()}**`, - response: { + cy.intercept(`${this.Urls.admin_deposit_list()}**`, { + body: { 'draw': 10, 'recordsTotal': testDeposits.length, 'recordsFiltered': testDeposits.length, 'data': testDeposits } }).as('listDeposits'); cy.location('pathname') .should('be.equal', this.Urls.admin_deposit()); cy.url().should('include', '/admin/deposit'); cy.get('#swh-admin-deposit-list') .should('exist'); cy.wait('@listDeposits').then((xhr) => { cy.log('response:', xhr.response); cy.log(xhr.response.body); let deposits = xhr.response.body.data; cy.log('Deposits: ', deposits); expect(deposits.length).to.equal(testDeposits.length); cy.get('#swh-admin-deposit-list').find('tbody > tr').as('rows'); // only 2 entries cy.get('@rows').each((row, idx, collection) => { let deposit = deposits[idx]; let responseDeposit = testDeposits[idx]; assert.isNotNull(deposit); assert.isNotNull(responseDeposit); expect(deposit.id).to.be.equal(responseDeposit['id']); expect(deposit.external_id).to.be.equal(responseDeposit['external_id']); expect(deposit.status).to.be.equal(responseDeposit['status']); expect(deposit.status_detail).to.be.equal(responseDeposit['status_detail']); expect(deposit.swhid).to.be.equal(responseDeposit['swhid']); expect(deposit.swhid_context).to.be.equal(responseDeposit['swhid_context']); let expectedOrigin = expectedOrigins[deposit.id]; // ensure it's in the dom cy.contains(deposit.id).should('be.visible'); if (deposit.status !== 'rejected') { expect(row).to.not.contain(deposit.external_id); cy.contains(expectedOrigin).should('be.visible'); } cy.contains(deposit.status).should('be.visible'); // those are hidden by default, so now visible if (deposit.status_detail !== null) { cy.contains(deposit.status_detail).should('not.exist'); } // those are hidden by default if (deposit.swhid !== null) { cy.contains(deposit.swhid).should('not.exist'); cy.contains(deposit.swhid_context).should('not.exist'); } }); // toggling all links and ensure, the previous checks are inverted cy.get('a.toggle-col').click({'multiple': true}).then(() => { cy.get('#swh-admin-deposit-list').find('tbody > tr').as('rows'); cy.get('@rows').each((row, idx, collection) => { let deposit = deposits[idx]; let expectedOrigin = expectedOrigins[deposit.id]; // ensure it's in the dom cy.contains(deposit.id).should('not.exist'); if (deposit.status !== 'rejected') { expect(row).to.not.contain(deposit.external_id); expect(row).to.contain(expectedOrigin); } expect(row).to.not.contain(deposit.status); // those are hidden by default, so now visible if (deposit.status_detail !== null) { cy.contains(deposit.status_detail).should('be.visible'); } // those are hidden by default, so now they should be visible if (deposit.swhid !== null) { cy.contains(deposit.swhid).should('be.visible'); cy.contains(deposit.swhid_context).should('be.visible'); // check SWHID link text formatting cy.contains(deposit.swhid_context).then(elt => { expect(elt[0].innerHTML).to.equal(deposit.swhid_context.replace(/;/g, ';
')); }); } }); }); cy.get('#swh-admin-deposit-list-error') .should('not.contain', 'An error occurred while retrieving the list of deposits'); }); }); }); diff --git a/cypress/integration/home.spec.js b/cypress/integration/home.spec.js index 597afbcf..0491e710 100644 --- a/cypress/integration/home.spec.js +++ b/cypress/integration/home.spec.js @@ -1,120 +1,108 @@ /** - * Copyright (C) 2019-2020 The Software Heritage developers + * Copyright (C) 2019-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ const $ = Cypress.$; const url = '/'; describe('Home Page Tests', function() { it('should have focus on search form after page load', function() { cy.visit(url); cy.get('#swh-origins-url-patterns') .should('have.attr', 'autofocus'); // for some reason, autofocus is not honored when running cypress tests // while it is in non controlled browsers // .should('have.focus'); }); it('should display positive stats for each category', function() { - cy.server(); - - cy.route({ - method: 'GET', - url: this.Urls.stat_counters() - }).as('getStatCounters'); + cy.intercept(this.Urls.stat_counters()) + .as('getStatCounters'); cy.visit(url) .wait('@getStatCounters') .wait(500) .get('.swh-counter:visible') .then((counters) => { for (let counter of counters) { let innerText = $(counter).text(); const value = parseInt(innerText.replace(/,/g, '')); assert.isAbove(value, 0); } }); }); it('should display null counters and hide history graphs when storage is empty', function() { - cy.server(); - - cy.route({ - method: 'GET', - url: this.Urls.stat_counters(), - response: { + cy.intercept(this.Urls.stat_counters(), { + body: { 'stat_counters': {}, 'stat_counters_history': {} } }).as('getStatCounters'); cy.visit(url) .wait('@getStatCounters') .wait(500) .get('.swh-counter:visible') .then((counters) => { for (let counter of counters) { const value = parseInt($(counter).text()); assert.equal(value, 0); } }); cy.get('.swh-counter-history') .should('not.be.visible'); }); it('should hide counters when data is missing', function() { - cy.server(); - - cy.route({ - method: 'GET', - url: this.Urls.stat_counters(), - response: { + cy.intercept(this.Urls.stat_counters(), { + body: { 'stat_counters': { 'content': 150, 'directory': 45, 'revision': 78 }, 'stat_counters_history': {} } }).as('getStatCounters'); cy.visit(url) .wait('@getStatCounters') .wait(500); cy.get('#swh-content-count, #swh-directory-count, #swh-revision-count') .should('be.visible'); cy.get('#swh-release-count, #swh-person-count, #swh-origin-count') .should('not.be.visible'); cy.get('.swh-counter-history') .should('not.be.visible'); }); it('should redirect to search page when submitting search form', function() { const searchText = 'git'; cy.get('#swh-origins-url-patterns') .type(searchText) .get('.swh-search-icon') .click(); cy.location('pathname') .should('equal', this.Urls.browse_search()); cy.location('search') .should('equal', `?q=${searchText}&with_visit=true&with_content=true`); }); }); diff --git a/cypress/integration/layout.spec.js b/cypress/integration/layout.spec.js index fcef8bce..5ac2774c 100644 --- a/cypress/integration/layout.spec.js +++ b/cypress/integration/layout.spec.js @@ -1,227 +1,229 @@ /** - * Copyright (C) 2019-2020 The Software Heritage developers + * Copyright (C) 2019-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ const url = '/browse/help/'; const statusUrl = 'https://status.softwareheritage.org'; describe('Test top-bar', function() { beforeEach(function() { cy.clearLocalStorage(); cy.visit(url); }); it('should should contain all navigation links', function() { cy.get('.swh-top-bar a') .should('have.length.of.at.least', 4) .and('be.visible') .and('have.attr', 'href'); }); it('should show donate button on lg screen', function() { cy.get('.swh-donate-link') .should('be.visible'); }); it('should hide donate button on sm screen', function() { cy.viewport(600, 800); cy.get('.swh-donate-link') .should('not.be.visible'); }); it('should hide full width switch on small screens', function() { cy.viewport(360, 740); cy.get('#swh-full-width-switch-container') .should('not.be.visible'); cy.viewport(600, 800); cy.get('#swh-full-width-switch-container') .should('not.be.visible'); cy.viewport(800, 600); cy.get('#swh-full-width-switch-container') .should('not.be.visible'); }); it('should show full width switch on large screens', function() { cy.viewport(1024, 768); cy.get('#swh-full-width-switch-container') .should('be.visible'); cy.viewport(1920, 1080); cy.get('#swh-full-width-switch-container') .should('be.visible'); }); it('should change container width when toggling Full width switch', function() { cy.get('#swh-web-content') .should('have.class', 'container') .should('not.have.class', 'container-fluid'); cy.should(() => { expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.null; }); cy.get('#swh-full-width-switch') .click({force: true}); cy.get('#swh-web-content') .should('not.have.class', 'container') .should('have.class', 'container-fluid'); cy.should(() => { expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.true; }); cy.get('#swh-full-width-switch') .click({force: true}); cy.get('#swh-web-content') .should('have.class', 'container') .should('not.have.class', 'container-fluid'); cy.should(() => { expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.false; }); }); it('should restore container width when loading page again', function() { cy.visit(url) .get('#swh-web-content') .should('have.class', 'container') .should('not.have.class', 'container-fluid'); cy.get('#swh-full-width-switch') .click({force: true}); cy.visit(url) .get('#swh-web-content') .should('not.have.class', 'container') .should('have.class', 'container-fluid'); cy.get('#swh-full-width-switch') .click({force: true}); cy.visit(url) .get('#swh-web-content') .should('have.class', 'container') .should('not.have.class', 'container-fluid'); }); function genStatusResponse(status, statusCode) { return { 'result': { 'status': [ { 'id': '5f7c4c567f50b304c1e7bd5f', 'name': 'Save Code Now', 'updated': '2020-11-30T13:51:21.151Z', 'status': 'Operational', 'status_code': 100 }, { 'id': '5f7c4c6f8338bc04b7f476fe', 'name': 'Source Code Crawlers', 'updated': '2020-11-30T13:51:21.151Z', 'status': status, 'status_code': statusCode } ] } }; } it('should display swh status widget when data are available', function() { const statusTestData = [ { status: 'Operational', statusCode: 100, color: 'green' }, { status: 'Scheduled Maintenance', statusCode: 200, color: 'blue' }, { status: 'Degraded Performance', statusCode: 300, color: 'yellow' }, { status: 'Partial Service Disruption', statusCode: 400, color: 'yellow' }, { status: 'Service Disruption', statusCode: 500, color: 'red' }, { status: 'Security Event', statusCode: 600, color: 'red' } ]; + const responses = []; + for (let std of statusTestData) { + responses.push(genStatusResponse(std.status, std.statusCode)); + } + for (let std of statusTestData) { - cy.server(); - cy.route({ - url: `${statusUrl}/**`, - response: genStatusResponse(std.status, std.statusCode) - }).as('getSwhStatusData'); cy.visit(url); + // trick to override the response of an intercepted request + // https://github.com/cypress-io/cypress/issues/9302 + cy.intercept(`${statusUrl}/**`, req => req.reply(responses.shift())) + .as('getSwhStatusData'); cy.wait('@getSwhStatusData'); cy.get('.swh-current-status-indicator').should('have.class', std.color); cy.get('#swh-current-status-description').should('have.text', std.status); } }); it('should not display swh status widget when data are not available', function() { - cy.server(); - cy.route({ - url: `${statusUrl}/**`, - response: {} + cy.intercept(`${statusUrl}/**`, { + body: {} }).as('getSwhStatusData'); cy.visit(url); cy.wait('@getSwhStatusData'); cy.get('.swh-current-status').should('not.exist'); }); }); describe('Test navbar', function() { it('should redirect to search page when submitting search form in navbar', function() { const keyword = 'python'; cy.get('#swh-origins-search-top-input') .type(keyword); cy.get('.swh-search-navbar') .submit(); cy.url() .should('include', `${this.Urls.browse_search()}?q=${keyword}`); }); }); describe('Test footer', function() { beforeEach(function() { cy.visit(url); }); it('should be visible', function() { cy.get('footer') .should('be.visible'); }); it('should have correct copyright years', function() { const currentYear = new Date().getFullYear(); const copyrightText = '(C) 2015–' + currentYear.toString(); cy.get('footer') .should('contain', copyrightText); }); it('should contain link to Web API', function() { cy.get('footer') .get(`a[href="${this.Urls.api_1_homepage()}"]`) .should('contain', 'Web API'); }); }); diff --git a/cypress/integration/origin-save.spec.js b/cypress/integration/origin-save.spec.js index 38be236a..147c679f 100644 --- a/cypress/integration/origin-save.spec.js +++ b/cypress/integration/origin-save.spec.js @@ -1,362 +1,357 @@ /** - * Copyright (C) 2019-2020 The Software Heritage developers + * Copyright (C) 2019-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ let url; let origin; const $ = Cypress.$; const saveCodeMsg = { 'success': 'The "save code now" request has been accepted and will be processed as soon as possible.', 'warning': 'The "save code now" request has been put in pending state and may be accepted for processing after manual review.', 'rejected': 'The "save code now" request has been rejected because the provided origin url is blacklisted.', 'rateLimit': 'The rate limit for "save code now" requests has been reached. Please try again later.', 'unknownError': 'An unexpected error happened when submitting the "save code now request', 'csrfError': 'CSRF Failed: Referrer checking failed - no Referrer.' }; function makeOriginSaveRequest(originType, originUrl) { cy.get('#swh-input-origin-url') .type(originUrl) .get('#swh-input-visit-type') .select(originType) .get('#swh-save-origin-form') .submit(); } function checkAlertVisible(alertType, msg) { cy.get('#swh-origin-save-request-status') .should('be.visible') .find(`.alert-${alertType}`) .should('be.visible') .and('contain', msg); } // Stub requests to save an origin function stubSaveRequest({ requestUrl, visitType = 'git', saveRequestStatus, originUrl, saveTaskStatus, responseStatus = 200, errorMessage = '', saveRequestDate = new Date(), visitDate = new Date() } = {}) { let response; if (responseStatus !== 200 && errorMessage) { response = { 'detail': errorMessage }; } else { response = genOriginSaveResponse({visitType: visitType, saveRequestStatus: saveRequestStatus, originUrl: originUrl, saveRequestDate: saveRequestDate, saveTaskStatus: saveTaskStatus, visitDate: visitDate}); } - cy.route({ - method: 'POST', - status: responseStatus, - url: requestUrl, - response: response - }).as('saveRequest'); + cy.intercept('POST', requestUrl, {body: response, statusCode: responseStatus}) + .as('saveRequest'); } // Mocks API response : /save/(:visit_type)/(:origin_url) // visit_type : {'git', 'hg', 'svn'} function genOriginSaveResponse({ visitType = 'git', saveRequestStatus, originUrl, saveRequestDate = new Date(), saveTaskStatus, visitDate = new Date() } = {}) { return { 'visit_type': visitType, 'save_request_status': saveRequestStatus, 'origin_url': originUrl, 'id': 1, 'save_request_date': saveRequestDate ? saveRequestDate.toISOString() : null, 'save_task_status': saveTaskStatus, 'visit_date': visitDate ? visitDate.toISOString() : null }; }; describe('Origin Save Tests', function() { before(function() { url = this.Urls.origin_save(); origin = this.origin[0]; this.originSaveUrl = this.Urls.origin_save_request(origin.type, origin.url); }); beforeEach(function() { cy.fixture('origin-save').as('originSaveJSON'); cy.fixture('save-task-info').as('saveTaskInfoJSON'); cy.visit(url); - cy.server(); }); it('should display accepted message when accepted', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'accepted', originUrl: origin.url, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should validate gitlab subproject url', function() { const gitlabSubProjectUrl = 'https://gitlab.com/user/project/sub/'; const originSaveUrl = this.Urls.origin_save_request('git', gitlabSubProjectUrl); stubSaveRequest({requestUrl: originSaveUrl, saveRequestStatus: 'accepted', originurl: gitlabSubProjectUrl, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', gitlabSubProjectUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should validate project url with _ in username', function() { const gitlabSubProjectUrl = 'https://gitlab.com/user_name/project.git'; const originSaveUrl = this.Urls.origin_save_request('git', gitlabSubProjectUrl); stubSaveRequest({requestUrl: originSaveUrl, saveRequestStatus: 'accepted', originurl: gitlabSubProjectUrl, saveTaskStatus: 'not yet scheduled'}); makeOriginSaveRequest('git', gitlabSubProjectUrl); cy.wait('@saveRequest').then(() => { checkAlertVisible('success', saveCodeMsg['success']); }); }); it('should display warning message when pending', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'pending', originUrl: origin.url, saveTaskStatus: 'not created'}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('warning', saveCodeMsg['warning']); }); }); it('should show error when csrf validation failed (status: 403)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'rejected', originUrl: origin.url, saveTaskStatus: 'not created', responseStatus: 403, errorMessage: saveCodeMsg['csrfError']}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['csrfError']); }); }); it('should show error when origin is rejected (status: 403)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'rejected', originUrl: origin.url, saveTaskStatus: 'not created', responseStatus: 403, errorMessage: saveCodeMsg['rejected']}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['rejected']); }); }); it('should show error when rate limited (status: 429)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'Request was throttled. Expected available in 60 seconds.', originUrl: origin.url, saveTaskStatus: 'not created', responseStatus: 429}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['rateLimit']); }); }); it('should show error when unknown error occurs (status other than 200, 403, 429)', function() { stubSaveRequest({requestUrl: this.originSaveUrl, saveRequestStatus: 'Error', originUrl: origin.url, saveTaskStatus: 'not created', responseStatus: 406}); makeOriginSaveRequest(origin.type, origin.url); cy.wait('@saveRequest').then(() => { checkAlertVisible('danger', saveCodeMsg['unknownError']); }); }); it('should display origin save info in the requests table', function() { - cy.route('GET', '/save/requests/list/**', '@originSaveJSON'); + cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}); cy.get('#swh-origin-save-requests-list-tab').click(); cy.get('tbody tr').then(rows => { let i = 0; for (let row of rows) { const cells = row.cells; const requestDateStr = new Date(this.originSaveJSON.data[i].save_request_date).toLocaleString(); const saveStatus = this.originSaveJSON.data[i].save_task_status; assert.equal($(cells[0]).text(), requestDateStr); assert.equal($(cells[1]).text(), this.originSaveJSON.data[i].visit_type); let html = ''; if (saveStatus === 'succeeded') { let browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${encodeURIComponent(this.originSaveJSON.data[i].origin_url)}`; browseOriginUrl += `&timestamp=${encodeURIComponent(this.originSaveJSON.data[i].visit_date)}`; html += `${this.originSaveJSON.data[i].origin_url}`; } else { html += this.originSaveJSON.data[i].origin_url; } html += ` `; html += ''; assert.equal($(cells[2]).html(), html); assert.equal($(cells[3]).text(), this.originSaveJSON.data[i].save_request_status); assert.equal($(cells[4]).text(), saveStatus); ++i; } }); }); it('should not add timestamp to the browse origin URL is no visit date has been found', function() { const originUrl = 'https://git.example.org/example.git'; const saveRequestData = genOriginSaveResponse({ saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'succeeded', visitDate: null }); const saveRequestsListData = { 'recordsTotal': 1, 'draw': 2, 'recordsFiltered': 1, 'data': [saveRequestData] }; - cy.route('GET', '/save/requests/list/**', saveRequestsListData); + cy.intercept('/save/requests/list/**', {body: saveRequestsListData}); cy.get('#swh-origin-save-requests-list-tab').click(); cy.get('tbody tr').then(rows => { const firstRowCells = rows[0].cells; const browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${encodeURIComponent(originUrl)}`; const browseOriginLink = `${originUrl}`; expect($(firstRowCells[2]).html()).to.have.string(browseOriginLink); }); }); it('should display/close task info popover when clicking on the info button', function() { - cy.route('GET', '/save/requests/list/**', '@originSaveJSON'); - cy.route('GET', '/save/task/info/**', '@saveTaskInfoJSON'); + cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}); + cy.intercept('/save/task/info/**', {fixture: 'save-task-info'}); cy.get('#swh-origin-save-requests-list-tab').click(); cy.get('.swh-save-request-info') .eq(0) .click(); cy.get('.swh-save-request-info-popover') .should('be.visible'); cy.get('.swh-save-request-info') .eq(0) .click(); cy.get('.swh-save-request-info-popover') .should('not.exist'); }); it('should hide task info popover when clicking on the close button', function() { - cy.route('GET', '/save/requests/list/**', '@originSaveJSON'); - cy.route('GET', '/save/task/info/**', '@saveTaskInfoJSON'); + cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}); + cy.intercept('/save/task/info/**', {fixture: 'save-task-info'}); cy.get('#swh-origin-save-requests-list-tab').click(); cy.get('.swh-save-request-info') .eq(0) .click(); cy.get('.swh-save-request-info-popover') .should('be.visible'); cy.get('.swh-save-request-info-close') .click(); cy.get('.swh-save-request-info-popover') .should('not.exist'); }); it('should fill save request form when clicking on "Save again" button', function() { - cy.route('GET', '/save/requests/list/**', '@originSaveJSON'); + cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}); cy.get('#swh-origin-save-requests-list-tab').click(); cy.get('.swh-save-origin-again') .eq(0) .click(); cy.get('tbody tr').eq(0).then(row => { const cells = row[0].cells; cy.get('#swh-input-visit-type') .should('have.value', $(cells[1]).text()); cy.get('#swh-input-origin-url') .should('have.value', $(cells[2]).text().slice(0, -1)); }); }); it('should select correct visit type if possible when clicking on "Save again" button', function() { const originUrl = 'https://gitlab.inria.fr/solverstack/maphys/maphys/'; const badVisitType = 'hg'; const goodVisitType = 'git'; - cy.route('GET', '/save/requests/list/**', '@originSaveJSON'); + cy.intercept('/save/requests/list/**', {fixture: 'origin-save'}); stubSaveRequest({requestUrl: this.Urls.origin_save_request(badVisitType, originUrl), visitType: badVisitType, saveRequestStatus: 'accepted', originUrl: originUrl, saveTaskStatus: 'failed', responseStatus: 200, errorMessage: saveCodeMsg['accepted']}); makeOriginSaveRequest(badVisitType, originUrl); cy.get('#swh-origin-save-requests-list-tab').click(); cy.wait('@saveRequest').then(() => { cy.get('.swh-save-origin-again') .eq(0) .click(); cy.get('tbody tr').eq(0).then(row => { const cells = row[0].cells; cy.get('#swh-input-visit-type') .should('have.value', goodVisitType); cy.get('#swh-input-origin-url') .should('have.value', $(cells[2]).text().slice(0, -1)); }); }); }); }); diff --git a/cypress/integration/origin-search.spec.js b/cypress/integration/origin-search.spec.js index 4956cf68..cde9680e 100644 --- a/cypress/integration/origin-search.spec.js +++ b/cypress/integration/origin-search.spec.js @@ -1,515 +1,503 @@ /** * Copyright (C) 2019-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ const nonExistentText = 'NoMatchExists'; let origin; let url; function doSearch(searchText) { cy.get('#swh-origins-url-patterns') .type(searchText) .get('.swh-search-icon') .click(); } function searchShouldRedirect(searchText, redirectUrl) { doSearch(searchText); cy.location('pathname') .should('equal', redirectUrl); } function searchShouldShowNotFound(searchText, msg) { doSearch(searchText); cy.get('#swh-no-result') .should('be.visible') .and('contain', msg); } function stubOriginVisitLatestRequests(status = 200, response = {type: 'tar'}) { - cy.server(); - cy.route({ - method: 'GET', - url: '**/visit/latest/**', - response: response, - status: status + cy.intercept('**/visit/latest/**', { + body: response, + statusCode: status }).as('originVisitLatest'); } describe('Test origin-search', function() { before(function() { origin = this.origin[0]; url = this.Urls.browse_search(); }); beforeEach(function() { cy.visit(url); }); it('should have focus on search form after page load', function() { cy.get('#swh-origins-url-patterns') .should('have.attr', 'autofocus'); // for some reason, autofocus is not honored when running cypress tests // while it is in non controlled browsers // .should('have.focus'); }); it('should show in result when url is searched', function() { cy.get('#swh-origins-url-patterns') .type(origin.url); cy.get('.swh-search-icon') .click(); cy.get('#origin-search-results') .should('be.visible'); cy.contains('tr', origin.url) .should('be.visible') .find('.swh-visit-status') .find('i') .should('have.class', 'mdi-check-bold') .and('have.attr', 'title', 'Software origin has been archived by Software Heritage'); const browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${encodeURIComponent(origin.url)}`; cy.get('tr a') .should('have.attr', 'href', browseOriginUrl); }); it('should remove origin URL with no archived content', function() { stubOriginVisitLatestRequests(404); cy.get('#swh-origins-url-patterns') .type(origin.url); cy.get('.swh-search-icon') .click(); cy.wait('@originVisitLatest'); cy.get('#origin-search-results') .should('be.visible') .find('tbody tr').should('have.length', 0); stubOriginVisitLatestRequests(200, {}); cy.get('.swh-search-icon') .click(); cy.wait('@originVisitLatest'); cy.get('#origin-search-results') .should('be.visible') .find('tbody tr').should('have.length', 0); }); it('should filter origins by visit type', function() { cy.intercept('**/visit/latest/**').as('checkOriginVisits'); cy.get('#swh-origins-url-patterns') .type('http'); for (let visitType of ['git', 'tar']) { cy.get('#swh-search-visit-type') .select(visitType); cy.get('.swh-search-icon') .click(); cy.wait('@checkOriginVisits'); cy.get('#origin-search-results') .should('be.visible'); cy.get('tbody tr td.swh-origin-visit-type').then(elts => { for (let elt of elts) { cy.get(elt).should('have.text', visitType); } }); } }); it('should show not found message when no repo matches', function() { searchShouldShowNotFound(nonExistentText, 'No origins matching the search criteria were found.'); }); it('should add appropriate URL parameters', function() { // Check all three checkboxes and check if // correct url params are added cy.get('#swh-search-origins-with-visit') .check({force: true}) .get('#swh-filter-empty-visits') .check({force: true}) .get('#swh-search-origin-metadata') .check({force: true}) .then(() => { const searchText = origin.url; doSearch(searchText); cy.location('search').then(locationSearch => { const urlParams = new URLSearchParams(locationSearch); const query = urlParams.get('q'); const withVisit = urlParams.has('with_visit'); const withContent = urlParams.has('with_content'); const searchMetadata = urlParams.has('search_metadata'); assert.strictEqual(query, searchText); assert.strictEqual(withVisit, true); assert.strictEqual(withContent, true); assert.strictEqual(searchMetadata, true); }); }); }); it('should search in origin intrinsic metadata', function() { cy.intercept('GET', '**/origin/metadata-search/**').as( 'originMetadataSearch' ); cy.get('#swh-search-origins-with-visit') .check({force: true}) .get('#swh-filter-empty-visits') .check({force: true}) .get('#swh-search-origin-metadata') .check({force: true}) .then(() => { const searchText = 'plugin'; doSearch(searchText); console.log(searchText); cy.wait('@originMetadataSearch').then((req) => { expect(req.response.body[0].metadata.metadata.description).to.equal( 'Line numbering plugin for Highlight.js' // metadata is defined in _TEST_ORIGINS variable in swh/web/tests/data.py ); }); }); }); it('should not send request to the resolve endpoint', function() { - cy.server(); + cy.intercept(`${this.Urls.api_1_resolve_swhid('').slice(0, -1)}**`) + .as('resolveSWHID'); - cy.route({ - method: 'GET', - url: `${this.Urls.api_1_resolve_swhid('').slice(0, -1)}**` - }).as('resolveSWHID'); - - cy.route({ - method: 'GET', - url: `${this.Urls.api_1_origin_search(origin.url)}**` - }).as('searchOrigin'); + cy.intercept(`${this.Urls.api_1_origin_search(origin.url)}**`) + .as('searchOrigin'); cy.get('#swh-origins-url-patterns') .type(origin.url); + cy.get('.swh-search-icon') .click(); cy.wait('@searchOrigin'); cy.xhrShouldBeCalled('resolveSWHID', 0); cy.xhrShouldBeCalled('searchOrigin', 1); }); context('Test pagination', function() { it('should not paginate if there are not many results', function() { // Setup search cy.get('#swh-search-origins-with-visit') .uncheck({force: true}) .get('#swh-filter-empty-visits') .uncheck({force: true}) .then(() => { const searchText = 'libtess'; // Get first page of results doSearch(searchText); cy.get('.swh-search-result-entry') .should('have.length', 1); cy.get('.swh-search-result-entry#origin-0 td a') .should('have.text', 'https://github.com/memononen/libtess2'); cy.get('#origins-prev-results-button') .should('have.class', 'disabled'); cy.get('#origins-next-results-button') .should('have.class', 'disabled'); }); }); it('should paginate forward when there are many results', function() { stubOriginVisitLatestRequests(); // Setup search cy.get('#swh-search-origins-with-visit') .uncheck({force: true}) .get('#swh-filter-empty-visits') .uncheck({force: true}) .then(() => { const searchText = 'many.origins'; // Get first page of results doSearch(searchText); cy.wait('@originVisitLatest'); cy.get('.swh-search-result-entry') .should('have.length', 100); cy.get('.swh-search-result-entry#origin-0 td a') .should('have.text', 'https://many.origins/1'); cy.get('.swh-search-result-entry#origin-99 td a') .should('have.text', 'https://many.origins/100'); cy.get('#origins-prev-results-button') .should('have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); // Get second page of results cy.get('#origins-next-results-button a') .click(); cy.wait('@originVisitLatest'); cy.get('.swh-search-result-entry') .should('have.length', 100); cy.get('.swh-search-result-entry#origin-0 td a') .should('have.text', 'https://many.origins/101'); cy.get('.swh-search-result-entry#origin-99 td a') .should('have.text', 'https://many.origins/200'); cy.get('#origins-prev-results-button') .should('not.have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); // Get third (and last) page of results cy.get('#origins-next-results-button a') .click(); cy.wait('@originVisitLatest'); cy.get('.swh-search-result-entry') .should('have.length', 50); cy.get('.swh-search-result-entry#origin-0 td a') .should('have.text', 'https://many.origins/201'); cy.get('.swh-search-result-entry#origin-49 td a') .should('have.text', 'https://many.origins/250'); cy.get('#origins-prev-results-button') .should('not.have.class', 'disabled'); cy.get('#origins-next-results-button') .should('have.class', 'disabled'); }); }); it('should paginate backward from a middle page', function() { stubOriginVisitLatestRequests(); // Setup search cy.get('#swh-search-origins-with-visit') .uncheck({force: true}) .get('#swh-filter-empty-visits') .uncheck({force: true}) .then(() => { const searchText = 'many.origins'; // Get first page of results doSearch(searchText); cy.wait('@originVisitLatest'); cy.get('#origins-prev-results-button') .should('have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); // Get second page of results cy.get('#origins-next-results-button a') .click(); cy.wait('@originVisitLatest'); cy.get('#origins-prev-results-button') .should('not.have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); // Get first page of results again cy.get('#origins-prev-results-button a') .click(); cy.wait('@originVisitLatest'); cy.get('.swh-search-result-entry') .should('have.length', 100); cy.get('.swh-search-result-entry#origin-0 td a') .should('have.text', 'https://many.origins/1'); cy.get('.swh-search-result-entry#origin-99 td a') .should('have.text', 'https://many.origins/100'); cy.get('#origins-prev-results-button') .should('have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); }); }); it('should paginate backward from the last page', function() { stubOriginVisitLatestRequests(); // Setup search cy.get('#swh-search-origins-with-visit') .uncheck({force: true}) .get('#swh-filter-empty-visits') .uncheck({force: true}) .then(() => { const searchText = 'many.origins'; // Get first page of results doSearch(searchText); cy.wait('@originVisitLatest'); cy.get('#origins-prev-results-button') .should('have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); // Get second page of results cy.get('#origins-next-results-button a') .click(); cy.wait('@originVisitLatest'); cy.get('#origins-prev-results-button') .should('not.have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); // Get third (and last) page of results cy.get('#origins-next-results-button a') .click(); cy.get('#origins-prev-results-button') .should('not.have.class', 'disabled'); cy.get('#origins-next-results-button') .should('have.class', 'disabled'); // Get second page of results again cy.get('#origins-prev-results-button a') .click(); cy.wait('@originVisitLatest'); cy.get('.swh-search-result-entry') .should('have.length', 100); cy.get('.swh-search-result-entry#origin-0 td a') .should('have.text', 'https://many.origins/101'); cy.get('.swh-search-result-entry#origin-99 td a') .should('have.text', 'https://many.origins/200'); cy.get('#origins-prev-results-button') .should('not.have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); // Get first page of results again cy.get('#origins-prev-results-button a') .click(); cy.wait('@originVisitLatest'); cy.get('.swh-search-result-entry') .should('have.length', 100); cy.get('.swh-search-result-entry#origin-0 td a') .should('have.text', 'https://many.origins/1'); cy.get('.swh-search-result-entry#origin-99 td a') .should('have.text', 'https://many.origins/100'); cy.get('#origins-prev-results-button') .should('have.class', 'disabled'); cy.get('#origins-next-results-button') .should('not.have.class', 'disabled'); }); }); }); context('Test valid SWHIDs', function() { it('should resolve directory', function() { const redirectUrl = this.Urls.browse_directory(origin.content[0].directory); const swhid = `swh:1:dir:${origin.content[0].directory}`; searchShouldRedirect(swhid, redirectUrl); }); it('should resolve revision', function() { const redirectUrl = this.Urls.browse_revision(origin.revisions[0]); const swhid = `swh:1:rev:${origin.revisions[0]}`; searchShouldRedirect(swhid, redirectUrl); }); it('should resolve snapshot', function() { const redirectUrl = this.Urls.browse_snapshot_directory(origin.snapshot); const swhid = `swh:1:snp:${origin.snapshot}`; searchShouldRedirect(swhid, redirectUrl); }); it('should resolve content', function() { const redirectUrl = this.Urls.browse_content(`sha1_git:${origin.content[0].sha1git}`); const swhid = `swh:1:cnt:${origin.content[0].sha1git}`; searchShouldRedirect(swhid, redirectUrl); }); it('should not send request to the search endpoint', function() { - cy.server(); const swhid = `swh:1:rev:${origin.revisions[0]}`; - cy.route({ - method: 'GET', - url: this.Urls.api_1_resolve_swhid(swhid) - }).as('resolveSWHID'); + cy.intercept(this.Urls.api_1_resolve_swhid(swhid)) + .as('resolveSWHID'); - cy.route({ - method: 'GET', - url: `${this.Urls.api_1_origin_search('').slice(0, -1)}**` - }).as('searchOrigin'); + cy.intercept(`${this.Urls.api_1_origin_search('').slice(0, -1)}**`) + .as('searchOrigin'); cy.get('#swh-origins-url-patterns') .type(swhid); + cy.get('.swh-search-icon') .click(); cy.wait('@resolveSWHID'); cy.xhrShouldBeCalled('resolveSWHID', 1); cy.xhrShouldBeCalled('searchOrigin', 0); }); }); context('Test invalid SWHIDs', function() { it('should show not found for directory', function() { const swhid = `swh:1:dir:${this.unarchivedRepo.rootDirectory}`; const msg = `Directory with sha1_git ${this.unarchivedRepo.rootDirectory} not found`; searchShouldShowNotFound(swhid, msg); }); it('should show not found for snapshot', function() { const swhid = `swh:1:snp:${this.unarchivedRepo.snapshot}`; const msg = `Snapshot with id ${this.unarchivedRepo.snapshot} not found!`; searchShouldShowNotFound(swhid, msg); }); it('should show not found for revision', function() { const swhid = `swh:1:rev:${this.unarchivedRepo.revision}`; const msg = `Revision with sha1_git ${this.unarchivedRepo.revision} not found.`; searchShouldShowNotFound(swhid, msg); }); it('should show not found for content', function() { const swhid = `swh:1:cnt:${this.unarchivedRepo.content[0].sha1git}`; const msg = `Content with sha1_git checksum equals to ${this.unarchivedRepo.content[0].sha1git} not found!`; searchShouldShowNotFound(swhid, msg); }); }); }); diff --git a/cypress/integration/vault.spec.js b/cypress/integration/vault.spec.js index 1b589fcc..faa6ffb6 100644 --- a/cypress/integration/vault.spec.js +++ b/cypress/integration/vault.spec.js @@ -1,566 +1,505 @@ /** - * Copyright (C) 2019-2020 The Software Heritage developers + * Copyright (C) 2019-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ let vaultItems = []; const progressbarColors = { 'new': 'rgba(128, 128, 128, 0.5)', 'pending': 'rgba(0, 0, 255, 0.5)', 'done': 'rgb(92, 184, 92)' }; function checkVaultCookingTask(objectType) { cy.contains('button', 'Download') .click(); cy.contains('.dropdown-item', objectType) .click(); cy.wait('@checkVaultCookingTask'); } function updateVaultItemList(vaultUrl, vaultItems) { cy.visit(vaultUrl) .then(() => { // Add uncooked task to localStorage // which updates it in vault items list window.localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultItems)); }); } // Mocks API response : /api/1/vault/(:objectType)/(:hash) // objectType : {'directory', 'revision'} function genVaultCookingResponse(objectType, objectId, status, message, fetchUrl) { return { 'obj_type': objectType, 'id': 1, 'progress_message': message, 'status': status, 'obj_id': objectId, 'fetch_url': fetchUrl }; }; // Tests progressbar color, status // And status in localStorage function testStatus(taskId, color, statusMsg, status) { cy.get(`.swh-vault-table #vault-task-${taskId}`) .should('be.visible') .find('.progress-bar') .should('be.visible') .and('have.css', 'background-color', color) .and('contain', statusMsg) .then(() => { // Vault item with object_id as taskId should exist in localStorage const currentVaultItems = JSON.parse(window.localStorage.getItem('swh-vault-cooking-tasks')); const vaultItem = currentVaultItems.find(obj => obj.object_id === taskId); assert.isNotNull(vaultItem); assert.strictEqual(vaultItem.status, status); }); } describe('Vault Cooking User Interface Tests', function() { before(function() { const dirInfo = this.origin[0].directory[0]; this.directory = dirInfo.id; this.directoryUrl = this.Urls.browse_origin_directory() + `?origin_url=${this.origin[0].url}&path=${dirInfo.path}`; this.vaultDirectoryUrl = this.Urls.api_1_vault_cook_directory(this.directory); this.vaultFetchDirectoryUrl = this.Urls.api_1_vault_fetch_directory(this.directory); this.revision = this.origin[1].revisions[0]; this.revisionUrl = this.Urls.browse_revision(this.revision); this.vaultRevisionUrl = this.Urls.api_1_vault_cook_revision_gitfast(this.revision); this.vaultFetchRevisionUrl = this.Urls.api_1_vault_fetch_revision_gitfast(this.revision); const release = this.origin[1].release; this.releaseUrl = this.Urls.browse_release(release.id) + `?origin_url=${this.origin[1].url}`; this.vaultReleaseDirectoryUrl = this.Urls.api_1_vault_cook_directory(release.directory); vaultItems[0] = { 'object_type': 'revision', 'object_id': this.revision, 'email': '', 'status': 'done', 'fetch_url': `/api/1/vault/revision/${this.revision}/gitfast/raw/`, 'progress_message': null }; }); beforeEach(function() { this.genVaultDirCookingResponse = (status, message = null) => { return genVaultCookingResponse('directory', this.directory, status, message, this.vaultFetchDirectoryUrl); }; this.genVaultRevCookingResponse = (status, message = null) => { return genVaultCookingResponse('revision', this.revision, status, message, this.vaultFetchRevisionUrl); }; - cy.server(); }); it('should report an error when vault service is experiencing issues', function() { // Browse a directory cy.visit(this.directoryUrl); // Stub responses when requesting the vault API to simulate // an internal server error - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: {'exception': 'APIError'}, - status: 500 + cy.intercept(this.vaultDirectoryUrl, { + body: {'exception': 'APIError'}, + statusCode: 500 }).as('checkVaultCookingTask'); cy.contains('button', 'Download') .click(); // Check error alert is displayed cy.get('.alert-danger') .should('be.visible') .should('contain', 'Archive cooking service is currently experiencing issues.'); }); it('should report an error when a cooking task creation failed', function() { // Browse a directory cy.visit(this.directoryUrl); // Stub responses when requesting the vault API to simulate // a task can not be created - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: {'exception': 'NotFoundExc'} + cy.intercept('GET', this.vaultDirectoryUrl, { + body: {'exception': 'NotFoundExc'} }).as('checkVaultCookingTask'); - cy.route({ - method: 'POST', - url: this.vaultDirectoryUrl, - response: {'exception': 'ValueError'}, - status: 500 + cy.intercept('POST', this.vaultDirectoryUrl, { + body: {'exception': 'ValueError'}, + statusCode: 500 }).as('createVaultCookingTask'); cy.contains('button', 'Download') .click(); // Create a vault cooking task through the GUI cy.get('.modal-dialog') .contains('button:visible', 'Ok') .click(); cy.wait('@createVaultCookingTask'); // Check error alert is displayed cy.get('.alert-danger') .should('be.visible') .should('contain', 'Archive cooking request submission failed.'); }); it('should create a directory cooking task and report the success', function() { // Browse a directory cy.visit(this.directoryUrl); + // Stub response to the vault API to simulate archive download + cy.intercept('GET', this.vaultFetchDirectoryUrl, { + fixture: `${this.directory}.tar.gz`, + headers: { + 'Content-disposition': `attachment; filename=${this.directory}.tar.gz`, + 'Content-Type': 'application/gzip' + } + }).as('fetchCookedArchive'); + + // Stub responses when checking vault task status + const checkVaulResponses = [ + {'exception': 'NotFoundExc'}, + this.genVaultDirCookingResponse('new'), + this.genVaultDirCookingResponse('pending', 'Processing...'), + this.genVaultDirCookingResponse('done') + ]; + + // trick to override the response of an intercepted request + // https://github.com/cypress-io/cypress/issues/9302 + cy.intercept('GET', this.vaultDirectoryUrl, req => req.reply(checkVaulResponses.shift())) + .as('checkVaultCookingTask'); + // Stub responses when requesting the vault API to simulate // a task has been created - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: {'exception': 'NotFoundExc'} - }).as('checkVaultCookingTask'); - - cy.route({ - method: 'POST', - url: this.vaultDirectoryUrl, - response: this.genVaultDirCookingResponse('new') + cy.intercept('POST', this.vaultDirectoryUrl, { + body: this.genVaultDirCookingResponse('new') }).as('createVaultCookingTask'); cy.contains('button', 'Download') .click(); - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: this.genVaultDirCookingResponse('new') - }).as('checkVaultCookingTask'); - cy.window().then(win => { const swhIdsContext = win.swh.webapp.getSwhIdsContext(); const browseDirectoryUrl = swhIdsContext.directory.swhid_with_context_url; // Create a vault cooking task through the GUI cy.get('.modal-dialog') .contains('button:visible', 'Ok') .click(); cy.wait('@createVaultCookingTask'); // Check success alert is displayed cy.get('.alert-success') .should('be.visible') .should('contain', 'Archive cooking request successfully submitted.'); // Go to Downloads page cy.visit(this.Urls.browse_vault()); cy.wait('@checkVaultCookingTask').then(() => { testStatus(this.directory, progressbarColors['new'], 'new', 'new'); }); - // Stub response to the vault API indicating the task is processing - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: this.genVaultDirCookingResponse('pending', 'Processing...') - }).as('checkVaultCookingTask'); - cy.wait('@checkVaultCookingTask').then(() => { testStatus(this.directory, progressbarColors['pending'], 'Processing...', 'pending'); }); - // Stub response to the vault API indicating the task is finished - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: this.genVaultDirCookingResponse('done') - }).as('checkVaultCookingTask'); - cy.wait('@checkVaultCookingTask').then(() => { testStatus(this.directory, progressbarColors['done'], 'done', 'done'); }); - // Stub response to the vault API to simulate archive download - cy.route({ - method: 'GET', - url: this.vaultFetchDirectoryUrl, - response: `fx:${this.directory}.tar.gz`, - headers: { - 'Content-disposition': `attachment; filename=${this.directory}.tar.gz`, - 'Content-Type': 'application/gzip' - } - }).as('fetchCookedArchive'); - cy.get(`#vault-task-${this.directory} .vault-origin a`) .should('contain', this.origin[0].url) .should('have.attr', 'href', `${this.Urls.browse_origin()}?origin_url=${this.origin[0].url}`); cy.get(`#vault-task-${this.directory} .vault-object-info a`) .should('have.text', this.directory) .should('have.attr', 'href', browseDirectoryUrl); cy.get(`#vault-task-${this.directory} .vault-dl-link button`) .click(); cy.wait('@fetchCookedArchive').then((xhr) => { assert.isNotNull(xhr.response.body); }); }); }); it('should create a revision cooking task and report its status', function() { cy.adminLogin(); // Browse a revision cy.visit(this.revisionUrl); + // Stub response to the vault API indicating to simulate archive download + cy.intercept(this.vaultFetchRevisionUrl, { + fixture: `${this.revision}.gitfast.gz`, + headers: { + 'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`, + 'Content-Type': 'application/gzip' + } + }).as('fetchCookedArchive'); + + // Stub responses when checking vault task status + const checkVaultResponses = [ + {'exception': 'NotFoundExc'}, + this.genVaultRevCookingResponse('new'), + this.genVaultRevCookingResponse('pending', 'Processing...'), + this.genVaultRevCookingResponse('done') + ]; + + // trick to override the response of an intercepted request + // https://github.com/cypress-io/cypress/issues/9302 + cy.intercept('GET', this.vaultRevisionUrl, req => req.reply(checkVaultResponses.shift())) + .as('checkVaultCookingTask'); + // Stub responses when requesting the vault API to simulate // a task has been created - cy.route({ - method: 'GET', - url: this.vaultRevisionUrl, - response: {'exception': 'NotFoundExc'} - }).as('checkVaultCookingTask'); - - cy.route({ - method: 'POST', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('new') + cy.intercept('POST', this.vaultRevisionUrl, { + body: this.genVaultRevCookingResponse('new') }).as('createVaultCookingTask'); // Create a vault cooking task through the GUI checkVaultCookingTask('as git'); - cy.route({ - method: 'GET', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('new') - }).as('checkVaultCookingTask'); - cy.window().then(win => { const swhIdsContext = win.swh.webapp.getSwhIdsContext(); const browseRevisionUrl = swhIdsContext.revision.swhid_url; // Create a vault cooking task through the GUI cy.get('.modal-dialog') .contains('button:visible', 'Ok') .click(); cy.wait('@createVaultCookingTask'); // Check success alert is displayed cy.get('.alert-success') .should('be.visible') .should('contain', 'Archive cooking request successfully submitted.'); // Go to Downloads page cy.visit(this.Urls.browse_vault()); cy.wait('@checkVaultCookingTask').then(() => { testStatus(this.revision, progressbarColors['new'], 'new', 'new'); }); - // Stub response to the vault API indicating the task is processing - cy.route({ - method: 'GET', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('pending', 'Processing...') - }).as('checkVaultCookingTask'); - cy.wait('@checkVaultCookingTask').then(() => { testStatus(this.revision, progressbarColors['pending'], 'Processing...', 'pending'); }); - // Stub response to the vault API indicating the task is finished - cy.route({ - method: 'GET', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('done') - }).as('checkVaultCookingTask'); - cy.wait('@checkVaultCookingTask').then(() => { testStatus(this.revision, progressbarColors['done'], 'done', 'done'); }); - // Stub response to the vault API indicating to simulate archive - // download - cy.route({ - method: 'GET', - url: this.vaultFetchRevisionUrl, - response: `fx:${this.revision}.gitfast.gz`, - headers: { - 'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`, - 'Content-Type': 'application/gzip' - } - }).as('fetchCookedArchive'); - cy.get(`#vault-task-${this.revision} .vault-origin`) .should('have.text', 'unknown'); cy.get(`#vault-task-${this.revision} .vault-object-info a`) .should('have.text', this.revision) .should('have.attr', 'href', browseRevisionUrl); cy.get(`#vault-task-${this.revision} .vault-dl-link button`) .click(); cy.wait('@fetchCookedArchive').then((xhr) => { assert.isNotNull(xhr.response.body); }); }); }); it('should create a directory cooking task from the release view', function() { // Browse a directory cy.visit(this.releaseUrl); + // Stub responses when checking vault task status + const checkVaultResponses = [ + {'exception': 'NotFoundExc'}, + this.genVaultDirCookingResponse('new') + ]; + + // trick to override the response of an intercepted request + // https://github.com/cypress-io/cypress/issues/9302 + cy.intercept('GET', this.vaultReleaseDirectoryUrl, req => req.reply(checkVaultResponses.shift())) + .as('checkVaultCookingTask'); + // Stub responses when requesting the vault API to simulate // a task has been created - cy.route({ - method: 'GET', - url: this.vaultReleaseDirectoryUrl, - response: {'exception': 'NotFoundExc'} - }).as('checkVaultCookingTask'); - - cy.route({ - method: 'POST', - url: this.vaultReleaseDirectoryUrl, - response: this.genVaultDirCookingResponse('new') + cy.intercept('POST', this.vaultReleaseDirectoryUrl, { + body: this.genVaultDirCookingResponse('new') }).as('createVaultCookingTask'); cy.contains('button', 'Download') .click(); - cy.route({ - method: 'GET', - url: this.vaultReleaseDirectoryUrl, - response: this.genVaultDirCookingResponse('new') - }).as('checkVaultCookingTask'); - // Create a vault cooking task through the GUI cy.get('.modal-dialog') .contains('button:visible', 'Ok') .click(); cy.wait('@createVaultCookingTask'); // Check success alert is displayed cy.get('.alert-success') .should('be.visible') .should('contain', 'Archive cooking request successfully submitted.'); }); it('should offer to recook an archive if no more available to download', function() { updateVaultItemList(this.Urls.browse_vault(), vaultItems); // Send 404 when fetching vault item - cy.route({ - method: 'GET', - status: 404, - url: this.vaultFetchRevisionUrl, - response: { + cy.intercept(this.vaultFetchRevisionUrl, { + statusCode: 404, + body: { 'exception': 'NotFoundExc', 'reason': `Revision with ID '${this.revision}' not found.` }, headers: { 'Content-Type': 'json' } }).as('fetchCookedArchive'); cy.get(`#vault-task-${this.revision} .vault-dl-link button`) .click(); cy.wait('@fetchCookedArchive').then(() => { - cy.route({ - method: 'POST', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('new') + cy.intercept('POST', this.vaultRevisionUrl, { + body: this.genVaultRevCookingResponse('new') }).as('createVaultCookingTask'); - cy.route({ - method: 'GET', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('new') + cy.intercept(this.vaultRevisionUrl, { + body: this.genVaultRevCookingResponse('new') }).as('checkVaultCookingTask'); cy.get('#vault-recook-object-modal > .modal-dialog') .should('be.visible') .contains('button:visible', 'Ok') .click(); cy.wait('@createVaultCookingTask') .wait('@checkVaultCookingTask') .then(() => { testStatus(this.revision, progressbarColors['new'], 'new', 'new'); }); }); }); it('should remove selected vault items', function() { updateVaultItemList(this.Urls.browse_vault(), vaultItems); cy.get(`#vault-task-${this.revision}`) .find('input[type="checkbox"]') .click({force: true}); cy.contains('button', 'Remove selected tasks') .click(); cy.get(`#vault-task-${this.revision}`) .should('not.exist'); }); it('should offer to immediately download a directory tarball if already cooked', function() { // Browse a directory cy.visit(this.directoryUrl); - // Stub responses when requesting the vault API to simulate - // the directory tarball has already been cooked - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: this.genVaultDirCookingResponse('done') - }).as('checkVaultCookingTask'); - // Stub response to the vault API to simulate archive download - cy.route({ - method: 'GET', - url: this.vaultFetchDirectoryUrl, - response: `fx:${this.directory}.tar.gz`, + cy.intercept(this.vaultFetchDirectoryUrl, { + fixture: `${this.directory}.tar.gz`, headers: { 'Content-disposition': `attachment; filename=${this.directory}.tar.gz`, 'Content-Type': 'application/gzip' } }).as('fetchCookedArchive'); + // Stub responses when requesting the vault API to simulate + // the directory tarball has already been cooked + cy.intercept(this.vaultDirectoryUrl, { + body: this.genVaultDirCookingResponse('done') + }).as('checkVaultCookingTask'); + // Create a vault cooking task through the GUI cy.contains('button', 'Download') .click(); // Start archive download through the GUI cy.get('.modal-dialog') .contains('button:visible', 'Ok') .click(); cy.wait('@fetchCookedArchive'); }); it('should offer to immediately download a revision gitfast archive if already cooked', function() { cy.adminLogin(); // Browse a directory cy.visit(this.revisionUrl); - // Stub responses when requesting the vault API to simulate - // the directory tarball has already been cooked - cy.route({ - method: 'GET', - url: this.vaultRevisionUrl, - response: this.genVaultRevCookingResponse('done') - }).as('checkVaultCookingTask'); - // Stub response to the vault API to simulate archive download - cy.route({ - method: 'GET', - url: this.vaultFetchRevisionUrl, - response: `fx:${this.revision}.gitfast.gz`, + cy.intercept(this.vaultFetchRevisionUrl, { + fixture: `${this.revision}.gitfast.gz`, headers: { 'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`, 'Content-Type': 'application/gzip' } }).as('fetchCookedArchive'); + // Stub responses when requesting the vault API to simulate + // the directory tarball has already been cooked + cy.intercept(this.vaultRevisionUrl, { + body: this.genVaultRevCookingResponse('done') + }).as('checkVaultCookingTask'); + checkVaultCookingTask('as git'); // Start archive download through the GUI cy.get('.modal-dialog') .contains('button:visible', 'Ok') .click(); cy.wait('@fetchCookedArchive'); }); it('should offer to recook an object if previous vault task failed', function() { cy.visit(this.directoryUrl); // Stub responses when requesting the vault API to simulate // the last cooking of the directory tarball has failed - cy.route({ - method: 'GET', - url: this.vaultDirectoryUrl, - response: this.genVaultDirCookingResponse('failed') + cy.intercept(this.vaultDirectoryUrl, { + body: this.genVaultDirCookingResponse('failed') }).as('checkVaultCookingTask'); cy.contains('button', 'Download') .click(); // Check that recooking the directory is offered to user cy.get('.modal-dialog') .contains('button:visible', 'Ok') .should('be.visible'); }); }); diff --git a/cypress/support/index.js b/cypress/support/index.js index ffee5d29..9d789da8 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -1,152 +1,145 @@ /** * Copyright (C) 2019-2020 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ import '@cypress/code-coverage/support'; import {httpGetJson} from '../utils'; Cypress.Screenshot.defaults({ screenshotOnRunFailure: false }); Cypress.Commands.add('xhrShouldBeCalled', (alias, timesCalled) => { - expect( - cy.state('requests').filter(call => call.alias === alias), - `${alias} should have been called ${timesCalled} times` - ).to.have.length(timesCalled); + const testRoutes = cy.state('routes'); + const aliasRoute = Cypress._.find(testRoutes, {alias}); + expect(Object.keys(aliasRoute.requests || {})).to.have.length(timesCalled); }); Cypress.Commands.add('adminLogin', () => { const url = '/admin/login/'; return cy.request({ url: url, method: 'GET' }).then(() => { cy.getCookie('sessionid').should('not.exist'); cy.getCookie('csrftoken').its('value').then((token) => { cy.request({ url: url, method: 'POST', form: true, followRedirect: false, body: { username: 'admin', password: 'admin', csrfmiddlewaretoken: token } }).then(() => { cy.getCookie('sessionid').should('exist'); return cy.getCookie('csrftoken').its('value'); }); }); }); }); before(function() { this.unarchivedRepo = { url: 'https://github.com/SoftwareHeritage/swh-web', type: 'git', revision: '7bf1b2f489f16253527807baead7957ca9e8adde', snapshot: 'd9829223095de4bb529790de8ba4e4813e38672d', rootDirectory: '7d887d96c0047a77e2e8c4ee9bb1528463677663', content: [{ sha1git: 'b203ec39300e5b7e97b6e20986183cbd0b797859' }] }; this.origin = [{ url: 'https://github.com/memononen/libtess2', type: 'git', content: [{ path: 'Source/tess.h' }, { path: 'premake4.lua' }], directory: [{ path: 'Source', id: 'cd19126d815470b28919d64b2a8e6a3e37f900dd' }], revisions: [], invalidSubDir: 'Source1' }, { url: 'https://github.com/wcoder/highlightjs-line-numbers.js', type: 'git', content: [{ path: 'src/highlightjs-line-numbers.js' }], directory: [], revisions: ['1c480a4573d2a003fc2630c21c2b25829de49972'], release: { name: 'v2.6.0', id: '6877028d6e5412780517d0bfa81f07f6c51abb41', directory: '5b61d50ef35ca9a4618a3572bde947b8cccf71ad' } }]; const getMetadataForOrigin = async originUrl => { const originVisitsApiUrl = this.Urls.api_1_origin_visits(originUrl); const originVisits = await httpGetJson(originVisitsApiUrl); const lastVisit = originVisits[0]; const snapshotApiUrl = this.Urls.api_1_snapshot(lastVisit.snapshot); const lastOriginSnapshot = await httpGetJson(snapshotApiUrl); let revision = lastOriginSnapshot.branches.HEAD.target; if (lastOriginSnapshot.branches.HEAD.target_type === 'alias') { revision = lastOriginSnapshot.branches[revision].target; } const revisionApiUrl = this.Urls.api_1_revision(revision); const lastOriginHeadRevision = await httpGetJson(revisionApiUrl); return { 'directory': lastOriginHeadRevision.directory, 'revision': lastOriginHeadRevision.id, 'snapshot': lastOriginSnapshot.id }; }; cy.visit('/').window().then(async win => { this.Urls = win.Urls; for (let origin of this.origin) { const metadata = await getMetadataForOrigin(origin.url); const directoryApiUrl = this.Urls.api_1_directory(metadata.directory); origin.dirContent = await httpGetJson(directoryApiUrl); origin.rootDirectory = metadata.directory; origin.revisions.push(metadata.revision); origin.snapshot = metadata.snapshot; for (let content of origin.content) { const contentPathApiUrl = this.Urls.api_1_directory(origin.rootDirectory, content.path); const contentMetaData = await httpGetJson(contentPathApiUrl); content.name = contentMetaData.name.split('/').slice(-1)[0]; content.sha1git = contentMetaData.target; content.directory = contentMetaData.dir_id; content.rawFilePath = this.Urls.browse_content_raw(`sha1_git:${content.sha1git}`) + `?filename=${encodeURIComponent(content.name)}`; cy.request(content.rawFilePath) .then((response) => { const fileText = response.body; const fileLines = fileText.split('\n'); content.numberLines = fileLines.length; // If last line is empty its not shown if (!fileLines[content.numberLines - 1]) content.numberLines -= 1; }); } } }); }); - -// force the use of fetch polyfill wrapping XmlHttpRequest -// in order for cypress to be able to intercept and stub them -Cypress.on('window:before:load', win => { - win.fetch = null; -});